package com.mgy.db.sharding.utils;

import com.google.common.collect.Sets;
import com.mgy.db.TableInfo;
import com.mgy.db.TableRouter;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 解析分库分表参数
 *
 * @author mgy
 * @date 2019.09.03
 */
public final class DbUtil {
    /**
     * 分库号名称
     */
    public static final String DB_NO = "dbNo";

    /**
     * 分表号名称
     */
    public static final String TABLE_NO = "tableNo";

    /**
     * 逻辑表
     */
    public static final String LOGIC_TABLE = "logicTable";
    /**
     * 每个表对于的分库数量和分表数量
     */
    public static final ConcurrentHashMap<String, TableInfo> TABLES = new ConcurrentHashMap();
    /**
     * 数据源数量
     */
    public static int datasourceCount = 0;

    /**
     * 获取分表路由
     */
    public static Set<TableRouter> getTableRouters(String[] paramNames, Class[] paramTypes, Object[] paramValues, Annotation[][] paramAnnotations) {
        Set<TableRouter> tableRouters = new HashSet<>();
        if (paramNames == null || paramTypes == null || paramValues == null || paramAnnotations == null) {
            return null;
        }

        TableRouter tableRouter = null;
        for (int i = 0; i < paramNames.length; i++) {
            String name = paramNames[i];
            Class type = paramTypes[i];
            Object obj = paramValues[i];
            Annotation[] paramAnnotationArr = paramAnnotations[i];
            Param param = getParamAnnotation(paramAnnotationArr);

            if (BeanUtils.isSimpleValueType(type) && (DB_NO.equals(name) || param != null && DB_NO.equals(param.value()))) {
                if (tableRouter == null) {
                    tableRouter = new TableRouter();
                }
                tableRouter.setDbNo((Integer) obj);
            } else if (BeanUtils.isSimpleValueType(type) && (TABLE_NO.equals(name) || param != null && TABLE_NO.equals(param.value()))) {
                if (tableRouter == null) {
                    tableRouter = new TableRouter();
                }
                tableRouter.setTableNo((Integer) obj);
            } else if (BeanUtils.isSimpleValueType(type) && (LOGIC_TABLE.equals(name) || param != null && LOGIC_TABLE.equals(param.value()))) {
                if (tableRouter == null) {
                    tableRouter = new TableRouter();
                }
                tableRouter.setLogicTable((String) obj);
            } else if (TableRouter.class.isAssignableFrom(type) && obj != null) {
                TableRouter router = (TableRouter) obj;
                if (router.getDbNo() != null) {
                    tableRouters.add((TableRouter) obj);
                }
            } else if (obj instanceof Collection) {
                Collection collection = (Collection) obj;
                Iterator iterator = collection.iterator();
                while (iterator.hasNext()) {
                    Object itemObj = iterator.next();
                    if (itemObj != null) {
                        if (TableRouter.class == itemObj.getClass()) {
                            tableRouters.add((TableRouter) itemObj);
                        } else if (TableRouter.class.isAssignableFrom(itemObj.getClass())) {
                            TableRouter router = (TableRouter) itemObj;
                            if (router.getDbNo() != null) {
                                tableRouters.add((TableRouter) itemObj);
                            }
                            break;
                        } else {
                            TableRouter router = getTableRouterFromField(itemObj);
                            if (router != null) {
                                tableRouters.add(router);
                            }
                            break;
                        }
                    }
                }
            } else if (obj instanceof Map) {
                Map map = (Map) obj;
                Set<Map.Entry> set = map.entrySet();
                String logicTable = null;
                Integer dbNo = null;
                Integer tableNo = null;
                for (Map.Entry entry : set) {
                    Object key = entry.getKey();
                    Object value = entry.getValue();
                    if (key != null && value != null && BeanUtils.isSimpleValueType(key.getClass()) && BeanUtils.isSimpleValueType(value.getClass())
                            && key instanceof String && (DB_NO.equals(key) || TABLE_NO.equals(key) || LOGIC_TABLE.equals(key))) {
                        if (DB_NO.equals(key) && (value instanceof Integer || value instanceof Long || value instanceof Short)) {
                            dbNo = (int) value;
                        } else if (TABLE_NO.equals(key) && (value instanceof Integer || value instanceof Long || value instanceof Short)) {
                            tableNo = (int) value;
                        } else if (LOGIC_TABLE.equals(key) && value instanceof String) {
                            logicTable = value.toString();
                        }
                    }
                }
                if (dbNo != null) {
                    TableRouter temp = new TableRouter();
                    temp.setLogicTable(logicTable);
                    temp.setDbNo(dbNo);
                    temp.setTableNo(tableNo);
                    tableRouters.add(temp);
                }
            } else if (type.isArray()) {
                int len = Array.getLength(obj);
                for (int k = 0; k < len; k++) {
                    Object itemObj = Array.get(obj, k);
                    if (itemObj != null) {
                        if (TableRouter.class == itemObj.getClass()) {
                            tableRouters.add((TableRouter) itemObj);
                        } else if (TableRouter.class.isAssignableFrom(itemObj.getClass())) {
                            TableRouter router = (TableRouter) itemObj;
                            if (router.getDbNo() != null) {
                                tableRouters.add((TableRouter) itemObj);
                            }
                            break;
                        } else {
                            TableRouter router = getTableRouterFromField(itemObj);
                            if (router != null) {
                                tableRouters.add(router);
                            }
                            break;
                        }
                    }
                }
            } else {
                TableRouter router = getTableRouterFromField(obj);
                if (router != null) {
                    tableRouters.add(router);
                }
            }
        }
        if (tableRouter != null) {
            tableRouters.add(tableRouter);
        }
        return tableRouters;
    }


    private static Param getParamAnnotation(Annotation[] annotations) {
        if (annotations != null) {
            for (Annotation annotation : annotations) {
                if (annotation.annotationType() == Param.class) {
                    return (Param) annotation;
                }
            }
        }
        return null;
    }

    private static <T> T getFieldValue(Object obj, Class type, String fieldName) {
        try {
            Field[] fields = type.getDeclaredFields();
            for (Field field : fields) {
                if (BeanUtils.isSimpleValueType(field.getType())) {
                    if (field.getName().equals(fieldName)) {
                        field.setAccessible(true);
                        return (T) field.get(obj);
                    }
                }
            }
        } catch (IllegalAccessException e) {

        }
        if (type.getSuperclass() != null) {
            return getFieldValue(obj, type.getSuperclass(), fieldName);
        }
        return null;
    }

    private static TableRouter getTableRouterFromField(Object obj) {
        if (obj == null || BeanUtils.isSimpleValueType(obj.getClass()) || obj.getClass().isArray() || obj instanceof Collection || obj instanceof Map) {
            return null;
        }
        TableRouter tableRouter = null;

        try {
            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field field : fields) {
                if (BeanUtils.isSimpleValueType(field.getType())) {
                    if (DB_NO.equals(field.getName())) {
                        field.setAccessible(true);
                        Integer dbNo = (Integer) field.get(obj);
                        if (dbNo != null) {
                            if (tableRouter == null) {
                                tableRouter = new TableRouter();
                            }
                            tableRouter.setDbNo(dbNo);
                        }
                    } else if (TABLE_NO.equals(field.getName())) {
                        field.setAccessible(true);
                        Integer tableNo = (Integer) field.get(obj);
                        if (tableNo != null) {
                            if (tableRouter == null) {
                                tableRouter = new TableRouter();
                            }
                            tableRouter.setTableNo(tableNo);
                        }
                    } else if (LOGIC_TABLE.equals(field.getName())) {
                        field.setAccessible(true);
                        String logicTable = (String) field.get(obj);
                        if (logicTable != null && logicTable.length() > 0) {
                            if (tableRouter == null) {
                                tableRouter = new TableRouter();
                            }
                            tableRouter.setLogicTable(logicTable);
                        }
                    }
                }
            }
        } catch (IllegalAccessException e) {

        }
        if (tableRouter != null && tableRouter.getDbNo() != null) {
            return tableRouter;
        }
        return null;
    }

    /**
     * 判断字符串是否是数字
     *
     * @param str str
     * @return true:是数字,false:不是数字
     */
    public static boolean isNumeric(String str) {
        if (str == null || str.length() == 0) {
            return false;
        }
        for (int i = 0; i < str.length(); i++) {
            if (!Character.isDigit(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    public static TableRouter buildTableRouter(String logicTable, Integer dbNo, Integer tableNo) {
        TableRouter tableRouter = new TableRouter();
        tableRouter.setLogicTable(logicTable);
        tableRouter.setDbNo(dbNo);
        tableRouter.setTableNo(tableNo);
        return tableRouter;
    }

    public static TableRouter buildTableRouter(Integer dbNo, Integer tableNo) {
        return buildTableRouter(null, dbNo, tableNo);
    }

    public static <T extends TableRouter> T buildTableRouter(String logicTable, Integer dbNo, Integer tableNo, T obj) {
        if (obj == null) {
            return null;
        }
        obj.setLogicTable(logicTable);
        obj.setDbNo(dbNo);
        obj.setTableNo(tableNo);
        return obj;
    }

    /**
     * 只分库，不分表的话使用setDatabaseShardingValue避免sql的解析与重写，提高性能
     */
    public static Set<TableRouter> parseTableRouter(Set<TableRouter> tableRouters) {
        if (CollectionUtils.isEmpty(tableRouters)) {
            return tableRouters;
        }
        if (tableRouters.stream().filter(t -> t.getTableNo() != null).count() > 0) {
            return tableRouters;
        }
        if (tableRouters.stream().filter(t -> t.getDbNo() != null).mapToInt(t -> t.getDbNo()).distinct().count() > 1) {
            return tableRouters;
        }
        TableRouter tableRouter = tableRouters.iterator().next();
        tableRouter.setLogicTable(null);
        tableRouter.setTableNo(null);
        return Sets.newHashSet(tableRouter);
    }

}
